
package com.idealighter.game.games.wknh.manager;

import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import com.google.inject.Singleton;

import com.idealighter.game.common.CommonMsgMgr;
import com.idealighter.game.core.common.Game;
import com.idealighter.game.core.constant.Operator;
import com.idealighter.game.core.constant.notice.Notice;
import com.idealighter.game.core.constant.notice.NoticeType;
import com.idealighter.game.core.error.ErrorCode;
import com.idealighter.game.core.tuple.Triplet;
import com.idealighter.game.dblog.service.DbLogService;
import com.idealighter.game.dictionary.dic.WknhBatteryDic;
import com.idealighter.game.dictionary.dic.WknhFishDic;
import com.idealighter.game.dictionary.dic.WknhRoomDic;
import com.idealighter.game.dictionary.dic.WknhScenceDic;
import com.idealighter.game.dictionary.domain.WknhFishDomain;
import com.idealighter.game.dictionary.domain.WknhRoomDomain;
import com.idealighter.game.dictionary.domwrapper.WknhFishDomWrapper;
import com.idealighter.game.dictionary.domwrapper.WknhScenceDomWrapper;
import com.idealighter.game.event.manager.EventMgr;
import com.idealighter.game.event.struct.ClearGameDataEvent;
import com.idealighter.game.event.struct.PlayerExitEvent;
import com.idealighter.game.event.struct.ShutdownRoomEvent;
import com.idealighter.game.event.struct.StartRoomEvent;
import com.idealighter.game.gamehall.event.EnterRoomEvent;
import com.idealighter.game.gamehall.event.EnterTableEvent;
import com.idealighter.game.gamehall.event.ExitRoomEvent;
import com.idealighter.game.gamehall.event.ExitTableEvent;
import com.idealighter.game.gamehall.event.RoomMemInfoUpdateEvent;
import com.idealighter.game.games.common.AbstractRoom;
import com.idealighter.game.games.common.AbstractTable;
import com.idealighter.game.games.common.SeatState;
import com.idealighter.game.games.common.buyu.Bullet;
import com.idealighter.game.games.common.buyu.strategy.Strategy;
import com.idealighter.game.games.wknh.struct.WknhFish;
import com.idealighter.game.games.wknh.struct.WknhRoom;
import com.idealighter.game.games.wknh.struct.WknhScence;
import com.idealighter.game.games.wknh.struct.WknhSeat;
import com.idealighter.game.games.wknh.struct.WknhTable;
import com.idealighter.game.log.core.LogReason;
import com.idealighter.game.log.struct.game.Game21WinLoseLog;
import com.idealighter.game.log.struct.game.GameAfeeLog;
import com.idealighter.game.login.manager.LoginMgr;
import com.idealighter.game.notice.manager.NoticeMsgMgr;
import com.idealighter.game.player.manager.PlayerMgr;
import com.idealighter.game.player.struct.Player;
import com.idealighter.game.schedule.manager.ScheduleMgr;
import com.idealighter.game.server.ExecutorMgr;
import com.idealighter.game.server.core.executor.DisruptorExecutor;
import com.idealighter.utils.code.RandCodeUtil;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 悟空闹海 .
 * 
 * @date 2016年1月26日 下午8:36:26
 *
 */
@Singleton
public class WknhMgr {

  private static final Logger LOG = LoggerFactory.getLogger(WknhMgr.class);
  // 普通鱼
  public static final int GENERAL_FISH = 1;
  // 同类炸弹鱼
  public static final int KINDS_BOMB_FISH = 2;
  // 全屏炸弹鱼
  public static final int FULL_SCREEN_FISH = 3;
  // 场景延时5秒,每次刷场景的延时(客户端有个场景切换动画)
  public static final int SCENCE_DELAY = 5;

  private final EventMgr eventMgr;
  // 游戏线程executor,可以执行普通任务和定时任务
  public final DisruptorExecutor gameExecutor;

  @Inject
  private WknhDataMgr dataMgr;
  @Inject
  private PlayerMgr playerMgr;
  @Inject
  private CommonMsgMgr commonMsgMgr;
  @Inject
  private NoticeMsgMgr noticeMsgMgr;
  @Inject
  private WknhMsgMgr msgMgr;
  @Inject
  private WknhRoomDic roomDic;
  @Inject
  private WknhScenceDic scenceDic;
  @Inject
  private ScheduleMgr scheduleMgr;
  @Inject
  private WknhBatteryDic batteryDic;
  @Inject
  private WknhScheduleFishMgr scheduleFishMgr;
  @Inject
  private WknhFishDic fishDic;

  @Inject
  private LoginMgr loginMgr;
  @Inject
  private WknhControlScript controlScript;

  /**
   * 构造函数 .
   * 
   * @param eventMgr .
   * @param executorMgr .
   */
  @Inject
  public WknhMgr(EventMgr eventMgr, ExecutorMgr executorMgr) {
    this.eventMgr = eventMgr;
    eventMgr.register(this);
    this.gameExecutor = executorMgr.getGameExecutor(Game.WKNH.getModuleId());
  }

  /**
   * .
   * 
   * @param event .
   */
  @Subscribe
  public void onPlayerExitGame(PlayerExitEvent event) {
    gameExecutor.execute(() -> {
      Player player = event.player;
      long playerId = player.getId();

      WknhSeat seat = dataMgr.getPlayerSeat(playerId);
      if (seat != null) {
        doExitTabl(seat);
      }

      // 玩家是否在房间中
      if (player.curRoom instanceof WknhRoom) {
        // 退出房间
        exitRoom(player);
      }
    });
  }

  /**
   * .
   * 
   * @param event .
   */
  @Subscribe
  public void onStartRoom(StartRoomEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.WKNH) {
        roomDic.load();
      }
    });
  }

  /**
   * .
   * 
   * @param event .
   */
  @Subscribe
  public void onShutDownRoom(ShutdownRoomEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.WKNH) {
        roomDic.load();
        AbstractRoom room = dataMgr.getRoom(event.getRoomId());
        for (Long playerId : room.getPlayers()) {
          Player player = playerMgr.getPlayer(playerId);
          if (player != null) {
            loginMgr.noticeLogoutForGameExit(player);
          }
        }
      }

    });
  }

  @Subscribe
  public void onClearGameData(ClearGameDataEvent event) {
    gameExecutor.execute(() -> doClearGameData(event.playerId));
  }

  /**
   * 进入游戏大厅 .
   * 
   * @param player 玩家.
   */
  public void enterGameHall(Player player) {
    if (player.curRoom != null) {
      Game game = player.curRoom.game();
      LOG.warn("[悟空闹海]玩家[{}][{}]在[{}]游戏中，暂时无法进入游戏", player.getId(), player.getPlayerName(),
          game.getDesc());
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_ALREADY_IN, Operator.SYSTEM,
          game.getDesc());
      return;
    }

    msgMgr.sendEnterGameHallMsg(player);
    LOG.info("[悟空闹海]玩家[{}][{}]李逵劈鱼大厅成功", player.getPlayerName(), player.getId());
  }

  /**
   * 玩家进入房间 .
   * 
   * @param player 玩家.
   * @param roomId .
   */
  public void enterRoom(Player player, int roomId) {
    AbstractRoom curRoom = player.curRoom;
    if (curRoom != null) {
      Game game = curRoom.game();
      LOG.warn("[悟空闹海]玩家[{}][{}]当前在游戏[{}]房间[{}]中不能进入斗地主房间", player.getId(), player.getPlayerName(),
          curRoom.getId());
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_ALREADY_IN, Operator.SYSTEM,
          game.getDesc());
      return;
    }

    WknhRoom room = dataMgr.getRoom(roomId);
    if (room == null) {
      LOG.error("[悟空闹海]玩家[{}][{}]进入的房间[{}]不存在", player.getPlayerName(), player.getId(), roomId);
      return;
    }

    WknhRoomDomain roomDom = roomDic.get(roomId);
    if (roomDom.getIsActive() != 1) {
      LOG.error("[悟空闹海]玩家[{}][{}]进入的房间[{}]已经关闭", player.getPlayerName(), player.getId(), roomId);
      return;
    }
    if (player.getGold() < roomDom.getLower()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_NOT_ENOUGH, Operator.SYSTEM);
      LOG.warn("[悟空闹海]玩家[{}][{}]进入的房间[{}]低于下限[{}]金币", player.getPlayerName(), player.getId(),
          roomId, roomDom.getLower());
      return;
    }

    if (player.getGold() > roomDom.getUpper()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_BEYOND_MAX_GOLD, Operator.SYSTEM);
      LOG.warn("[悟空闹海]玩家[{}][{}]进入的房间[{}]高于上限[{}]金币", player.getPlayerName(), player.getId(),
          roomId, roomDom.getUpper());
      return;
    }

    if (!player.vip()
        && room.getPlayers().size() >= roomDom.getMaxNum() * roomDom.getOrdinarPeople() / 100) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_NOT_VIP_ROOM_FULL, Operator.SYSTEM);
      LOG.warn("[悟空闹海]玩家[{}][{}]进入的房间[{}]普通玩家人数已超过上限", player.getPlayerName(), player.getId(),
          roomId, roomDom.getMaxNum());
      return;
    }

    if (room.getPlayers().size() >= roomDom.getMaxNum()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_ROOM_FULL, Operator.SYSTEM);
      LOG.warn("[悟空闹海]玩家[{}][{}]进入的房间[{}]已超过最大人数[{}]", player.getPlayerName(), player.getId(),
          roomId, roomDom.getMaxNum());
      return;
    }

    // 修改玩家当前房间
    player.curRoom = room;
    room.getPlayers().add(player.getId());
    // 发送进入房间消息
    msgMgr.sendEnterRoomMsg(player);
    LOG.info("[悟空闹海]玩家[{}][{}]进入房间[{}]成功", player.getPlayerName(), player.getId(), roomId);

    // 触发进入房间事件
    eventMgr.post(new EnterRoomEvent(player, room));
  }

  /**
   * 玩家进入牌桌 .
   * 
   * @param player 玩家.
   * @param tableId .
   * @param order .
   */
  public void enterTable(Player player, int tableId, byte order) {
    if (!(player.curRoom instanceof WknhRoom)) {
      LOG.error("[悟空闹海]玩家[{}][{}]当前不在斗地主房间中不能进入斗地主桌子", player.getPlayerName(), player.getId());
      return;
    }

    WknhTable table = dataMgr.getTable(tableId);

    if (table == null) {
      LOG.error("[悟空闹海]玩家[{}][{}]进入的牌桌[{}]不存在", player.getPlayerName(), player.getId(), tableId);
      return;
    }

    if (dataMgr.getPlayerSeat(player.getId()) != null) {
      LOG.error("[悟空闹海]玩家[{}][{}]已经在牌桌中不能重复进入牌桌", player.getPlayerName(), player.getId());
      return;
    }

    WknhSeat seat = table.getSeat(order);
    if (seat == null) {
      LOG.error("[悟空闹海]玩家[{}][{}]进入的牌桌位置[{}]不合法", player.getPlayerName(), player.getId(), order);
      return;
    }

    if (seat.getPlayerId() > 0) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_SET_NOT_EMPTY, Operator.SYSTEM,
          seat.getPlayerName());
      LOG.warn("[悟空闹海]玩家[{}][{}]进入的牌桌位置[{}]已经有人", player.getPlayerName(), player.getId(), order);
      return;
    }

    /*
     * why?客户端进桌时只拿到同桌的playeId，从大厅的memifo中那里获取筹码等数据，由于捕鱼比较特殊，在开炮时没有广播分数变化( .
     * 太频繁了)，所以进桌前广播meminfo变化，客户端才能拿到最新的数据 . 现在都是直接取meminfo，不用广播了
     */
    /*
     * for . (WknhSeat s : table.seats) { if (s.playerId > 0) { // 触发房间大厅成员信息变更事件 eventMgr.post(new
     * RoomMemInfoUpdateEvent(playerMgr.getPlayer(s.playerId))); } }
     */

    // 进入座位
    doEnterTable(player, seat);
  }

  /**
   * 快速进入二人牛牛 .
   * 
   * @param player 玩家.
   * @param roomId .
   */
  public void fastEnterTable(Player player, int roomId) {
    if (!(player.curRoom instanceof WknhRoom)) {
      LOG.error("[悟空闹海]玩家[{}][{}]当前不在悟空闹海中不能直接进入桌子", player.getPlayerName(), player.getId());
      return;
    }

    WknhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat != null) {
      LOG.error("[悟空闹海]玩家[{}][{}]已经在牌桌中不能重复进入牌桌", player.getPlayerName(), player.getId());
      return;
    }

    WknhRoom room = dataMgr.getRoom(roomId);
    if (room == null) {
      LOG.error("[悟空闹海]玩家[{}][{}]快速进入的房间[{}]不存在", player.getPlayerName(), player.getId(), roomId);
      return;
    }

    WknhSeat emptySeat = findEmptySeat(player, room);
    if (emptySeat != null) {
      doEnterTable(player, emptySeat);
    } else {
      // 发送快速进入桌子结果
      msgMgr.sendFastEnterTableMsg(player, 1);
      commonMsgMgr.sendTipDlgMsg(player, ErrorCode.GAME_NOT_SIT, Operator.SYSTEM);
    }
  }

  /**
   * 查找空位置,优先查找有人的桌子 .
   * 
   * @param player 玩家.
   * @param room .
   * @return
   */
  private WknhSeat findEmptySeat(Player player, WknhRoom room) {
    WknhSeat emptySeat = null;
    ArrayList<WknhTable> tables = new ArrayList<>(room.tables.values());
    Collections.shuffle(tables);

    for (WknhTable table : tables) {
      boolean hasPlayer = false;
      WknhSeat tableEmptySeat = null;

      for (WknhSeat seat : table.seats) {
        if (seat.getPlayerId() == 0) {
          emptySeat = seat;
          tableEmptySeat = seat;
        } else {
          hasPlayer = true;
        }
      }

      if (hasPlayer && tableEmptySeat != null) {
        return tableEmptySeat;
      }
    }

    return emptySeat;
  }

  /**
   * 玩家进入座位 .
   * 
   * @param player 玩家.
   * @param seat 座位信息.
   */
  private void doEnterTable(Player player, WknhSeat seat) {
    int roomId = seat.table.room.getId().intValue();
    WknhTable table = seat.table;

    WknhRoomDomain roomDom = roomDic.get(seat.table.room.getId().intValue());
    if (player.getGold() < roomDom.getLower()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_NOT_ENOUGH, Operator.SYSTEM);
      LOG.warn("[悟空闹海]玩家[{}][{}]进入的房间[{}]低于下限[{}]金币", player.getPlayerName(), player.getId(),
          roomId, roomDom.getLower());
      return;
    }

    if (player.getGold() > roomDom.getUpper()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.PLAYER_GOLD_BEYOND_MAX_GOLD, Operator.SYSTEM);
      LOG.warn("[悟空闹海]玩家[{}][{}]进入的房间[{}]高于上限[{}]金币", player.getPlayerName(), player.getId(),
          roomId, roomDom.getUpper());
      return;
    }

    if (player.getGold() < roomDom.getAfee()) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_NOT_ENOUGH_FEE, Operator.SYSTEM);
      LOG.warn("[悟空闹海]玩家[{}][{}]进入四人牛牛桌子金币不足台费", player.getPlayerName(), player.getId());
      return;
    } else {
      playerMgr.minusGold(player, roomDom.getAfee(), LogReason.SHARK_ENTER_TABLE_MINUS_AFEE);
      DbLogService.log(new GameAfeeLog(player, table, roomDom.getAfee()));
    }

    seat.setPlayerId(player.getId());
    seat.setPlayerName(player.getPlayerName());
    seat.setSex(player.getSex());
    seat.setRobot(player.robot());
    seat.setState(SeatState.GAMING);

    if (!seat.isRobot()) {
      if (table.leader == 0) {
        table.leader = seat.getPlayerId();
        msgMgr.sendInsteadPlayersUpgradeMsg(table);
      }
    } else {
      if (table.leader != 0) {
        msgMgr.sendInsteadPlayersUpgradeMsg(table);
      }
    }

    // 更新玩家座位
    dataMgr.updatePlayerSeats(player.getId(), seat);
    // 触发进入桌子事件
    eventMgr.post(new EnterTableEvent(player, seat));

    LOG.info("[悟空闹海]玩家[{}][{}]进入桌子[{}]座位[{}]成功", player.getPlayerName(), player.getId(),
        table.getId(), seat.getOrder());
    if (table.playersNum() == 1) {
      scheduleStartNextScence(table, 0);
    }

    // 向玩家发送进入牌桌结果信息
    msgMgr.sendEnterTableMsg(player, table);
    msgMgr.sendOtherEnterTableMsg(seat);
  }

  /**
   * 发送场景数据 .
   * 
   * @param player 玩家.
   */
  public void sendScenceData(Player player) {
    WknhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.warn("[悟空闹海]玩家[{}][{}]还未进入桌子不能获取场景数据", player.getId(), player.getPlayerName());
      return;
    }

    WknhRoom room = seat.table.room;
    if (room.likui > 0) {
      msgMgr.sendLikuiUpgradeMsg(seat);
    }

    msgMgr.sendScenceMsg(player, seat.table);
  }

  /**
   * schedule开始下一个场景 .
   * 
   * @param table .
   * @param nextScenceIndex .
   */
  public void scheduleStartNextScence(WknhTable table, int nextScenceIndex) {
    // 清空上一个场景
    table.scence().reset();
    // 先发送切换场景消息，客户端播放切换场景动画
    msgMgr.sendSwitchSceneMsg(table, nextScenceIndex);
    table.pauseFire = true;
    table.scenceFuture = scheduleMgr.schedule(() -> {
      doStartScence(table, nextScenceIndex);
    }, SCENCE_DELAY, TimeUnit.SECONDS, gameExecutor);
  }

  /**
   * 开始场景 .
   * 
   * @param table .
   * @param scenceIndex .
   */
  private void doStartScence(WknhTable table, int scenceIndex) {
    table.scenceIndex = scenceIndex;
    WknhScence scence = table.scence();
    table.pauseFire = false;
    /*
     * schedule刷鱼策略 .
     */
    WknhScenceDomWrapper scenceDom = scenceDic.get(scence.id);
    for (Integer strategyId : scenceDom.getStrategysData()) {
      Strategy strategy = dataMgr.getStrategy(strategyId);
      scheduleFishMgr.scheduleProduceFishs(scence, strategy, null);
    }

    int nextScenceIndex = (scenceIndex + 1) % table.scences.size();
    // schedule下一场比赛
    table.scenceFuture = scheduleMgr.schedule(() -> {
      scheduleStartNextScence(table, nextScenceIndex);
    }, scenceDom.getLifeTime(), TimeUnit.SECONDS, gameExecutor);
  }

  /**
   * 玩家金币兑换筹码 .
   * 
   * @param player 玩家.
   * @param all . 是否全部兑换
   */
  public void exchangeChips(Player player, boolean all) {
    WknhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[悟空闹海]玩家[{}][{}]不在牌桌中不能兑换筹码", player.getPlayerName(), player.getId());
      return;
    }

    // 剩余可兑换的金币
    long leftExchanglbeGold =
        player.getGold() - convertGold(seat.table.room.getId().intValue(), seat.getTotalChips());

    // 兑换的金币
    long exchangeGold = 0;
    if (all) {
      exchangeGold = leftExchanglbeGold;
    } else {
      WknhRoomDomain roomDom = roomDic.get(seat.table.room.getId().intValue());
      exchangeGold = roomDom.getExchangePer() > leftExchanglbeGold ? leftExchanglbeGold
          : roomDom.getExchangePer();
    }

    if (exchangeGold == 0) {
      return;
    }

    if (player.getGold() < exchangeGold
        + convertGold(seat.table.room.getId().intValue(), seat.getTotalChips())) {
      LOG.error("[悟空闹海]玩家[{}][{}]兑换的金币[{}]加上筹码[{}]兑换的金币超过自己的金币总和[{}]", player.getPlayerName(),
          player.getId(), exchangeGold, seat.getTotalChips(), player.getGold());
      return;
    }

    long chips = convertChip(seat.table.room.getId().intValue(), exchangeGold);
    seat.setTotalChips(seat.getTotalChips() + chips);
    // 发送筹码变化消息
    msgMgr.sendChipsChangeMsg(seat);
    // playerMgr.minusGold(player, exchangeGold,
    // LogReason.JCBY_EXCHANGE_CHIPS);

    LOG.info("[悟空闹海]玩家[{}][{}]用金币[{}]兑换筹码[{}]成功", player.getPlayerName(), player.getId(),
        exchangeGold, chips);
    // 触发房间大厅成员信息变更事件
    eventMgr.post(new RoomMemInfoUpdateEvent(player));
  }

  /**
   * 玩家筹码兑换金币,捕鱼是全部下 .
   * 
   * @param player 玩家.
   */
  public void exchangeAllGold(Player player) {
    WknhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[悟空闹海]玩家[{}][{}]不在牌桌中不能兑换金币", player.getPlayerName(), player.getId());
      return;
    }
    if (seat.getTotalChips() <= 0) {
      return;
    }

    doExchangeAllGold(seat);
    // 触发房间大厅成员信息变更事件
    eventMgr.post(new RoomMemInfoUpdateEvent(player));
  }

  /**
   * 玩家所有筹码兑换金币 .
   * 
   * @param seat 座位信息.
   */
  private void doExchangeAllGold(WknhSeat seat) {
    long gold = convertGold(seat.table.room.getId().intValue(), seat.getTotalChips());
    seat.setTotalChips(0);
    // 发送兑换筹码变更
    msgMgr.sendChipsChangeMsg(seat);
    // playerMgr.addGold(seat.playerId, gold, LogReason.JCBY_EXCHANGE_GOLD);

    LOG.info("[悟空闹海]玩家[{}][{}]兑换金币[{}]成功", seat.getPlayerName(), seat.getPlayerId(), gold);
  }

  /**
   * 筹码换算金币,只会是多个金币等于一个筹码 .
   * 
   * @param roomId .
   * @param chips .
   */
  public long convertGold(int roomId, long chips) {
    WknhRoomDomain roomDom = roomDic.get(roomId);

    return chips * roomDom.getProportionGold() / roomDom.getProportionChips();
  }

  /**
   * 金币换算筹码 .
   * 
   * @param roomId .
   * @param gold .
   * @return
   */
  public long convertChip(int roomId, long gold) {
    WknhRoomDomain roomDomain = roomDic.get(roomId);

    return gold * roomDomain.getProportionChips() / roomDomain.getProportionGold();
  }

  /**
   * 切换炮台 .
   * 
   * @param player 玩家.
   * @param type . 类型(0:加炮,非0:减炮)
   */
  public void switchBattery(Player player, int type) {
    WknhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[悟空闹海]玩家[{}][{}]不在牌桌中不能切换炮台", player.getPlayerName(), player.getId());
      return;
    }

    if (seat.power) {
      LOG.error("[悟空闹海]玩家[{}][{}]当前是能量炮不能切换炮台", player.getPlayerName(), player.getId());
      return;
    }

    int nextBatteryId = 0;
    // 最大炮管id
    int maxBatteryId = batteryDic.list().size();
    if (type == 0) { // 加炮
      nextBatteryId = seat.batteryId + 1;
      if (nextBatteryId > maxBatteryId) {
        nextBatteryId = 1;
      }
    } else { // 减炮
      nextBatteryId = seat.batteryId - 1;
      if (nextBatteryId < 1) {
        nextBatteryId = maxBatteryId;
      }
    }
    seat.batteryId = nextBatteryId;
    msgMgr.sendBatteryChangeMsg(seat);
  }

  /**
   * 开火 .
   * 
   * @param player 玩家.
   * @param angle .
   */
  public void fire(Player player, int angle) {
    WknhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      return;
    }

    int score = batteryDic.get(seat.batteryId).getScore();
    if (seat.getTotalChips() < score || seat.table.pauseFire) {
      return;
    }

    seat.setTotalChips(seat.getTotalChips() - score);
    seat.winChips -= score;
    msgMgr.sendChipsChangeMsg(seat);

    int bulletId = seat.bulletSeed++;
    seat.bullets.put(bulletId, new Bullet(bulletId, score, seat.power));
    msgMgr.sendFireMsg(seat, bulletId, angle);
    playerMgr.addGold(player, -convertGold(seat.table.room.getId().intValue(), score), false, false,
        LogReason.WKNH_FIRE);
  }

  /**
   * 子弹打中鱼,某个客户端会代发机器人打中 .
   * 
   * @param hitPlayerId .
   * @param bulletId .
   * @param fishId . 鱼的唯一标识
   */
  public void hit(long hitPlayerId, int bulletId, int fishId) {
    WknhSeat seat = dataMgr.getPlayerSeat(hitPlayerId);
    if (seat == null) {
      return;
    }
    Map<Integer, Bullet> bullets = seat.bullets;
    WknhTable table = seat.table;
    WknhScence scence = table.scence();
    Bullet bullet = bullets.remove(bulletId);
    // 被打中的鱼
    WknhFish hitedFish = scence.fishs.get(fishId);
    if (bullet == null) {
      return;
    } else if (hitedFish == null || !hitedFish.alive()) { // 鱼不存在或鱼已经死了，奖池吃子弹
      controlScript.eatBullet(seat, bullet);
      return;
    }

    WknhRoom room = table.room;
    WknhFishDomWrapper fishDom = fishDic.get(hitedFish.fishId);
    WknhRoomDomain roomDom = roomDic.get(room.getId().intValue());
    int deathStrategy = fishDom.getDeathStrategy();
    Triplet<LinkedHashSet<WknhFish>, Long, Long> hitDiedData =
        controlScript.hitDied(seat, bullet, hitedFish);
    // 如果该鱼死亡的导致的鱼死亡集合
    LinkedHashSet<WknhFish> dieFishs = hitDiedData.v1;
    // 合计鱼的倍数
    long totalFishMultiple = hitDiedData.v2;
    // 合计鱼死亡获得的筹码
    long totalFishChips = hitDiedData.v3;
    // 打中鱼的子弹分数(能量炮子弹需要加倍)
    int bulletScore = bullet.power ? bullet.score * 2 : bullet.score;

    if (dieFishs != null) {
      // 被打死的鱼分数(key：鱼id,val:鱼的分数)
      Map<Integer, Integer> fishScores = new LinkedHashMap<>();
      for (WknhFish dieFish : dieFishs) {
        dieFish.died = true;
        int fishScore = bulletScore * dieFish.multiple;
        if (dieFish.fishId == room.likui) {
          // 子弹清0前倍率
          int preLikuiMultiple = room.likuiMultiple();
          room.likuiEatBullets = 0;
          // 子弹清0后倍率
          int likuiMultiple = room.likuiMultiple();
          if (likuiMultiple != preLikuiMultiple) { // 李逵等级变化了
            updateLikuiMultiple(room, likuiMultiple);
          }
        }

        fishScores.put(dieFish.id, fishScore);
        if (deathStrategy > 0) {
          scheduleFishMgr.scheduleProduceFishs(scence, dataMgr.getStrategy(deathStrategy),
              hitedFish.position());
        }

        // 鱼死后是否发送公告
        sendedNotice(seat, dieFish, fishScore);
      }

      // 能量炮处理
      if (!seat.power) {
        int powerMultiple = roomDom.getPowerBatteryMultiple();
        int powerPro = roomDom.getPowerBatteryPro();
        int powerTime = roomDom.getPowerBatteryTime();
        if (totalFishMultiple >= powerMultiple && RandCodeUtil.probable(powerPro, 10000)) {
          seat.power = true;
          msgMgr.sendBatteryChangeMsg(seat);
          scheduleMgr.schedule(() -> {
            seat.power = false;
            msgMgr.sendBatteryChangeMsg(seat);
          }, powerTime, TimeUnit.SECONDS, gameExecutor);
        }
      }

      seat.setTotalChips(seat.getTotalChips() + totalFishChips);
      seat.winChips += totalFishChips;
      playerMgr.addGold(playerMgr.selectPlayer(seat.getPlayerId()),
          convertGold(table.room.getId().intValue(), totalFishChips), false, LogReason.WKNH_HIT);
      msgMgr.sendHitsMsg(seat, fishScores);
      msgMgr.sendChipsChangeMsg(seat);
    } else if (hitedFish.fishId == room.likui) { // 李逵没死，吃子弹
      // 吃子弹前倍率
      int preLikuiMultiple = room.likuiMultiple();
      room.likuiEatBullets += bullet.score;
      // 吃子弹后倍率
      int likuiMultiple = room.likuiMultiple();
      if (likuiMultiple > preLikuiMultiple) { // 李逵升级了
        updateLikuiMultiple(room, likuiMultiple);
      }
    }
  }

  /**
   * 如果满足条件发送公告 .
   * 
   * @param seat 座位信息.
   * @param dieFish .
   * @param chips .
   */
  private void sendedNotice(WknhSeat seat, WknhFish dieFish, long chips) {
    WknhFishDomain fishDom = fishDic.get(dieFish.fishId);
    // 0:无公告,1:全服公告,2:本房间公告,只有 跑马灯公告
    int noticeType = fishDom.getNoticeType();

    if (noticeType > 0) {
      // 桌子号
      int tableIndex = seat.table.getId().intValue() % AbstractTable.MAX_TABLE;
      String sendContent =
          MessageFormat.format(fishDom.getNoticeContent(), tableIndex, seat.getPlayerName(), chips);
      if (noticeType == 1) {
        noticeMsgMgr.sendMarqueeNoticeMsg(sendContent, NoticeType.GAME, Notice.DEFAULT_INTERVAL,
            Notice.DEFAULT_TIMES, Notice.DEFAULT_COLOR, seat.table.room);
      } else if (noticeType == 2) {
        noticeMsgMgr.sendMarqueeNoticeMsg(sendContent, NoticeType.GAME, Notice.DEFAULT_INTERVAL,
            Notice.DEFAULT_TIMES, Notice.DEFAULT_COLOR);
      }
    }
  }

  /**
   * 更新李逵倍数 .
   * 
   * @param room .
   * @param multiple .
   */
  private void updateLikuiMultiple(WknhRoom room, int multiple) {
    for (WknhTable t : room.tables.values()) {
      for (WknhFish f : t.scence().fishs.values()) {
        if (f.fishId == room.likui) {
          f.multiple = multiple;
        }
      }
    }

    // 发送李逵升级消息
    msgMgr.sendLikuiUpgradeMsg(room, multiple);
  }

  /**
   * 锁定鱼 .
   * 
   * @param player 玩家.
   * @param fishId .
   */
  public void lock(Player player, int fishId) {
    WknhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      return;
    }
    // LOG.info("[悟空闹海]玩家[{}][{}][{}]锁定鱼[{}]", player.getPlayerName(),
    // player.getId(), seat.order, fishId);
    msgMgr.sendLockMsg(seat, fishId);
  }

  /**
   * 取消锁定 .
   * 
   * @param player 玩家.
   */
  public void cancelLock(Player player) {
    WknhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      return;
    }
    LOG.info("[悟空闹海]玩家[{}][{}]取消锁定", player.getPlayerName(), player.getId());
    msgMgr.sendCancelLockMsg(seat);
  }

  /**
   * 玩家退出牌桌 .
   * 
   * @param player 玩家.
   */
  public void exitTable(Player player) {
    WknhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[悟空闹海]玩家[{}][{}]没在牌桌中不能退出牌桌", player.getPlayerName(), player.getId());
      return;
    }

    playerMgr.noticeGold(player);

    // 退出桌子
    doExitTabl(seat);
  }

  /**
   * 退出桌子,返还筹码,桌子leader退出处理 .
   * 
   * @param seat 座位信息.
   */
  private void doExitTabl(WknhSeat seat) {
    final long playerId = seat.getPlayerId();
    final WknhTable table = seat.table;

    // 返还子弹筹码
    long returnBulletChips = 0;
    for (Bullet bullet : seat.bullets.values()) {
      returnBulletChips += bullet.score;
    }

    seat.setTotalChips(seat.getTotalChips() + returnBulletChips);
    seat.winChips += returnBulletChips;
    // 返还子弹的金币
    playerMgr.addGold(playerId, convertGold(table.room.getId().intValue(), returnBulletChips),
        LogReason.WKNH_EXIT_TABL);

    // 兑换所有筹码
    // doExchangeAllGold(seat);
    // 发送退出房间消息
    msgMgr.sendExitTableMsg(seat);
    dataMgr.removePlayerSeat(playerId);
    DbLogService.log(new Game21WinLoseLog(playerMgr.selectPlayer(playerId), seat, seat.winChips,
        convertGold(table.room.getId().intValue(), seat.winChips), LogReason.WKNH_EXIT_TABL));
    LOG.info("[悟空闹海]玩家[{}][{}]退出桌子[{}]", playerId, seat.getPlayerName(), seat.table.getId());
    seat.clear();

    if (table.leader == playerId) { // 桌子leader退出
      table.leader = 0;
      for (WknhSeat s : table.seats) {
        if (!s.isRobot() && s.getPlayerId() > 0) { // 找下一个leader代发碰撞
          table.leader = s.getPlayerId();
          msgMgr.sendInsteadPlayersUpgradeMsg(table);
          break;
        }
      }
    }
    if (table.playersNum() == 0) {
      table.scence().reset();
      table.reset();
    }

    // 触发退出桌子事件
    eventMgr.post(new ExitTableEvent(playerMgr.getPlayer(playerId), seat));
  }

  /**
   * 玩家退出房间 .
   * 
   * @param player 玩家.
   */
  public void exitRoom(Player player) {
    AbstractRoom curRoom = player.curRoom;
    WknhSeat seat = dataMgr.getPlayerSeat(player.getId());

    if (curRoom instanceof WknhRoom) {
      if (seat == null) {
        player.curRoom = null;
        WknhRoom room = (WknhRoom) curRoom;
        room.getPlayers().remove(player.getId());
        msgMgr.sendExitRoomMsg(player, 0);

        LOG.info("[悟空闹海]玩家[{}][{}]退出房间[{}]", player.getPlayerName(), player.getId(), room.getId());
        // 触发退出房间事件
        eventMgr.post(new ExitRoomEvent(player, curRoom));
      } else {
        LOG.error("[悟空闹海]玩家[{}][{}]在座位中不能退出房间,请先退出桌子", player.getPlayerName(), player.getId());
        msgMgr.sendExitRoomMsg(player, 1);
      }
    } else {
      LOG.warn("[悟空闹海]玩家[{}][{}]不在斗地主房间中，无法退出房间", player.getPlayerName(), player.getId());
    }
  }

  /**
   * 清除玩家,服务器出现bug，玩家卡在游戏中，后台可以清除玩家 .
   * 
   * @param playerId 玩家id.
   */
  private void doClearGameData(long playerId) {
    try {

      dataMgr.removePlayerSeat(playerId);
      /*
       * 清除玩家房间数据 .
       */
      for (WknhRoom room : dataMgr.rooms()) {
        room.getPlayers().remove(playerId);

        /*
         * 清除房间中玩家所在桌子数据 .
         */
        for (WknhTable table : room.tables.values()) {
          for (WknhSeat s : table.seats) {
            if (s.getPlayerId() > 0 && s.getPlayerId() == playerId) {
              dataMgr.removePlayerSeat(s.getPlayerId());
              room.getPlayers().remove(s.getPlayerId());

              /*
               * 清除座位数据 .
               */
              s.clear();
              Player player = playerMgr.getPlayer(s.getPlayerId());
              if (player != null) {
                loginMgr.noticeLogoutForGameExit(player);
              }
            }
          }
        }
      }

      Player player = playerMgr.getPlayer(playerId);
      if (player != null) {
        loginMgr.noticeLogoutForGameExit(player);
      }
    } catch (Exception e) {
      LOG.error("[悟空闹海]清除玩家[" + playerId + "]卡限失败", e);
    }
  }
}
